use tokio::io::AsyncReadExt;

use crate::{
    error::AppErr,
    serial_port::cframe::frame,
    utils::{new_buf, xor_sum},
};

use super::cframe::RecvFrame;

async fn sync<R: AsyncReadExt + Unpin>(r: &mut R) -> Result<usize, AppErr> {
    let mut buf = [0u8; 1];
    let mut flag = false;
    loop {
        let n = r.read(&mut buf).await?;
        if n <= 0 {
            return Err(AppErr::Closed("io closed"));
        }
        if flag && (buf[0] == frame::HEAD1) {
            break;
        }
        flag = buf[0] == frame::HEAD0;
    }
    let n = r.read(&mut buf).await?;
    if n <= 0 {
        return Err(AppErr::Closed("io closed"));
    }
    let len = buf[0] as usize;
    if len < frame::MIN_LEN {
        return Err(AppErr::Proto("len is invalid"));
    }
    Ok(len)
}

pub async fn read_frame<R: AsyncReadExt + Unpin>(r: &mut R) -> Result<RecvFrame, AppErr> {
    let len = sync(r).await?;

    let mut buf = new_buf(len - 3);
    r.read_exact(&mut buf).await?;

    let n = buf.len();
    let s = xor_sum(&buf[..(n - 2)]);
    if s != buf[n - 2] {
        return Err(AppErr::Proto("check sum invalid"));
    }

    if frame::END != buf[n - 1] {
        return Err(AppErr::Proto("frame end invalid"));
    }

    Ok(RecvFrame::new(buf))
}
